package com.cloud.school.app.config.security;

import com.alibaba.fastjson.JSON;
import com.cloud.school.app.config.CacheService;
import com.cloud.school.app.util.exception.UserNotLoginException;
import com.google.common.collect.Lists;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.cloud.school.app.config.security.SecurityUserUtils.LOGIN_USER;

/**
 * @ClassName: JWTUtils
 * @Description: JWTToken共通方法定义
 * @date: 2017年9月26日 下午6:21:30
 * @version: V1.0
 */
@Slf4j
public final class JWTUtils {

    public static final String USER_KEY = "name";

    public static final String AUTHORITIES_KEY = "auth";
    /**
     * @Fields AUTHORIZATION_HEADER : AUTHORIZATION_HEADER
     */
    public static final String AUTHORIZATION_HEADER = "Authorization";
    /**
     * @Fields AUTHORIZATION_HEADER_BEARER : Token的标志"Bearer后面的空格一定不能删除"
     */
    public static final String AUTHORIZATION_HEADER_BEARER = "Bearer ";

    /**
     * @Fields secretKey : 密钥
     */
    private static final String secretKey = "cloudSchool2018120XX#$%()(#*!()!KL<22127SecurityTestAZBCM";
    /**
     * @Fields tokenValidityInMilliseconds : 令牌的有效时间(毫秒)
     */
//    private static final long tokenValidityInMilliseconds = 1000 * 60 * 60 * 24 * 30l; //token失效时间
    public static final int tokenRefreshTimeInDays = 15;

    /**
     * @Title: resolveToken
     * @Description: 从Request请求头中获取token
     * @param: request HttpServletRequest
     * @return: String 用户的token
     */
    public static String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        return resolveToken(bearerToken);
    }

    /**
     * @Title: resolveToken
     * @Description: 从字符串截取token
     * @param: bearerToken 请求头中获取token
     * @return: String 用户的token
     */
    public static String resolveToken(String bearerToken) {
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(AUTHORIZATION_HEADER_BEARER)) {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }

    /**
     * @Title: validateToken
     * @Description: JWT Token验证检查
     * @param: secretKey 秘钥
     * @param: authToken 用户Token
     * @return: boolean boolean
     */
    public static boolean validateToken(String secretKey, String authToken) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            log.info("JWT签名无效......");
            log.debug("Invalid JWT signature trace: {}", e);
        } catch (MalformedJwtException e) {
            log.info("JWT Token 无效......");
            log.debug("Invalid JWT token trace: {}", e);
        } catch (ExpiredJwtException e) {
            log.info("JWT Token 已过期......");
            log.debug("Expired JWT token trace: {}", e);
        } catch (UnsupportedJwtException e) {
            log.info("JWT Token 不能被正常解析......");
            log.debug("Unsupported JWT token trace: {}", e);
        } catch (IllegalArgumentException e) {
            log.info("JWT token compact of handler are invalid.");
            log.debug("JWT token compact of handler are invalid trace: {}", e);
        }
        return false;
    }


    /**
     * @Title: createToken
     * @Description: 创建token
     * @param: userCode 用户UserCode
     * @param: rememberMe 是否记住登录
     * @return: String 生成的Token
     */
    public static String createToken(SecurityUserDetails user) {
        // 计算有效时间
//        long now = System.currentTimeMillis();
//        Date validity = new Date(now + tokenValidityInMilliseconds);
        // 创建token
        return Jwts.builder()
                // 放入UserName到Token中
                .setSubject(user.getUsername())
                // 放入UserId
                .setId(user.getUserId().toString())
                // 放入权限
                .claim(AUTHORITIES_KEY, JSON.toJSONString(user.getAuthorities()))
                // 放入密钥(公钥)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                // 放入创建token时的时间
//                .setExpiration(validity)   //不设定过期时间
                .compact();
    }

    /**
     * @Title: getAuthentication
     * @Description: 根据Token及Redis中的用户信息构造org.springframework.security.core.Authentication
     * @param: token 用户请求中的Token
     * @param: userInfo 用户信息(包含用户的权限信息)
     * @return: Authentication org.springframework.security.core.Authentication
     */
    public static Authentication getAuthentication(String token, CacheService cacheService) {
        // 解析用户传递过来的Token
        Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
        // 获取Token中的用户编码
        String username = null == claims.getSubject() ? "" : claims.getSubject();
        boolean isUserLogin = cacheService.hasKey(LOGIN_USER + username);
        if (!isUserLogin) {
            throw new UserNotLoginException("用户未登陆:" + username);
        }
        cacheService.expire(LOGIN_USER + username, tokenRefreshTimeInDays, TimeUnit.DAYS);
        Long userId = null == claims.getId() ? 0L : Long.parseLong(claims.getId());
        // 设置用户权限信息GrantedAuthority集合
        List<SecurityUserDetails.SecurityAuth> authorities = null == claims.get(AUTHORITIES_KEY) ? Lists.newArrayList() : JSON.parseArray(claims.get(AUTHORITIES_KEY).toString(), SecurityUserDetails.SecurityAuth.class);
        // 返回用户信息
        SecurityUserDetails principal = new SecurityUserDetails(userId, username, authorities);
        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }

    /**
     * @Title: validateToken
     * @Description: 验证JWT
     * @param: authToken 用户的Token
     * @return: boolean
     */
    public static boolean validateToken(String authToken) {
        return validateToken(secretKey, authToken);
    }
}